import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateArticleDto } from './dto/create-article.dto';
import { UpdateArticleDto } from './dto/update-article.dto';
import { Article } from './entities/article.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Brackets, Repository } from 'typeorm';
import { Tag } from '../tags/entities/tag.entity';
import { transformArticlesToLabelStrings, transformArticleToLabelStrings } from 'src/utils/transformArticlesToLabelStrings';
import { Category } from '../categories/entities/category.entity';
import { FindAllArticleDto } from './dto/findAll-article.dto';
import { ArticleTag } from '../common/entities/article-tag.entity';
import { ARTICLE_BROWSER, RedisService } from "../common/service/redis.service";

@Injectable()
export class ArticlesService {
  constructor(
    @InjectRepository(Article)
    private readonly articlesRepository: Repository<Article>,
    @InjectRepository(Tag)
    private readonly tagRepository: Repository<Tag>,
    @InjectRepository(ArticleTag)
    private readonly articleTagRepository: Repository<ArticleTag>,
    @InjectRepository(Category)
    private readonly categoryRepository: Repository<Category>,
    private readonly redisService: RedisService
  ) {}

  async create(createArticleDto: CreateArticleDto) {
    const tags = await Promise.all(createArticleDto.tags.map((label) => this.preloadTagByLabel(label)));
    const category = await this.preloadCategoryByTree(createArticleDto.category);
    const article = this.articlesRepository.create({
      ...createArticleDto,
      tags,
      category,
    });

    return transformArticleToLabelStrings(await this.articlesRepository.save(article));
  }

  async findAll(body: FindAllArticleDto) {
    const { page, pageSize, status, keyword, tags, category, years } = body;

    const query = this.articlesRepository
      .createQueryBuilder('article')
      .leftJoinAndSelect('article.tags', 'tag')
      .leftJoinAndSelect('article.category', 'category')
      .andWhere('article.status = :status', { status });

    if (tags && tags.length > 0) {
      const tagList = await this.tagRepository.createQueryBuilder('tag').select('tag.id').andWhere('tag.label in (:...tags)', { tags }).getMany();
      const tagIds = tagList.map((tag) => tag.id);
      const articleList = await this.articleTagRepository.createQueryBuilder('a').select('a.articleId').andWhere('a.tagId in (:...tagIds)', { tagIds }).getMany();
      const articleIds = articleList.map((articleId) => articleId.articleId);
      query.andWhere('article.id in (:...articleIds)', { articleIds });
    }

    if (category) {
      const { id } = await this.categoryRepository.findOne({
        where: { tree: category },
      });
      query.andWhere('article.categoryId = :category', { category: id });
    }

    if (keyword) {
      if (!isNaN(Number(keyword))) {
        query.andWhere('article.id = :keyword', { keyword });
      } else {
        query.andWhere('article.title LIKE :keyword OR article.description LIKE :keyword OR article.content LIKE :keyword', { keyword: `%${keyword}%` });
      }
    }

    if (years && years.length > 0) {
      const yearConditions = years.map((year) => `article.createTime BETWEEN '${year}-01-01' AND '${year}-12-31'`).join(' OR ');
      query.andWhere(new Brackets((qb) => qb.where(yearConditions)));
    }

    query.addOrderBy('article.updateTime', 'DESC').addOrderBy('article.createTime', 'DESC');

    const [articles, totalCount] = await query
      .skip((page - 1) * pageSize)
      .take(pageSize)
      .getManyAndCount();

    for (const article of articles) {
      article.browse = await this.redisService.get(ARTICLE_BROWSER + article.id)
    }

    return { articles: transformArticlesToLabelStrings(articles), totalCount };
  }

  async findOne(id: number) {
    const article = await this.articlesRepository.findOne({
      relations: ['tags', 'category'],
      where: { id },
    });
    await this.articlesRepository.update(article.id, { browse: article.browse + 1 })
    if (!article) throw new HttpException(`未找到文章 #${id}`, HttpStatus.NOT_FOUND);
    article.browse = this.redisService.get(ARTICLE_BROWSER + article.id)
    return transformArticleToLabelStrings(article);
  }

  async update(id: number, updateArticleDto: UpdateArticleDto) {
    const tags = updateArticleDto.tags && (await Promise.all(updateArticleDto.tags.map((label) => this.preloadTagByLabel(label))));

    const category = updateArticleDto.category && (await this.preloadCategoryByTree(updateArticleDto.category));

    const article = await this.articlesRepository.preload({
      id,
      ...updateArticleDto,
      tags,
      category,
    });

    if (!article) throw new HttpException(`未找到文章 #${id}`, HttpStatus.NOT_FOUND);

    return this.articlesRepository.save(article);
  }

  async remove(id: number) {
    const article = await this.articlesRepository.findOneBy({ id, status: 1 });
    if (!article) throw new HttpException(`未找到文章 #${id}`, HttpStatus.NOT_FOUND);
    article.deleteTime = new Date();
    article.status = -1;
    await this.articlesRepository.save(article);
    return `删除成功`;
  }

  private async preloadTagByLabel(label: string): Promise<Tag> {
    const existingTag = await this.tagRepository.findOne({ where: { label } });
    if (existingTag) return existingTag;
    return this.tagRepository.create({ label });
  }

  private async preloadCategoryByTree(chain: string): Promise<Category> {
    const existingCategory = await this.categoryRepository.findOne({
      where: { tree: chain },
    });
    if (existingCategory) return existingCategory;
    return this.categoryRepository.create({ tree: chain });
  }

  async findAllBrowse(){
    // 查询所有文章的id和browse字段
    return await this.articlesRepository.find({ select: ['id', 'browse'] });
  }
}
